<script setup lang="ts">
import type {CSSProperties} from 'vue';
import {computed, onMounted, onUnmounted, ref, unref,} from 'vue';

import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
import {useDebounceFn} from '@vueuse/core';

const emits = defineEmits(['reday', 'cropend', 'error',]);

const defaultOptions: Cropper.Options = {
  aspectRatio: 1,
  zoomable: true,
  zoomOnTouch: true,
  zoomOnWheel: true,
  cropBoxMovable: true,
  cropBoxResizable: true,
  toggleDragModeOnDblclick: true,
  autoCrop: true,
  background: true,
  highlight: true,
  center: true,
  responsive: true,
  restore: true,
  checkCrossOrigin: true,
  checkOrientation: true,
  scalable: true,
  modal: true,
  guides: true,
  movable: true,
  rotatable: true,
};

const props = defineProps({
  src: {type: String, required: true},
  circled: {type: Boolean, default: false},
  realTimePreview: {type: Boolean, default: true},
  height: {type: [String, Number], default: '300px'},
  crossorigin: {
    type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>,
    default: undefined,
  },
  options: {type: Object as PropType<Options>, default: () => ({})},
});

const ready = ref(false);
const imgElRef = ref();
const cropper = ref();

const initCropper = () => {
  const imgRef = unref(imgElRef);
  if (!imgRef) {
    return;
  }
  cropper.value = new Cropper(imgRef, {
    ...defaultOptions,
    ...props.options,
    ready() {
      ready.value = true;
      realtimeCropper();
      emits('reday', cropper.value);
    },
    crop() {
      debounceRealtimeCropper()
    },
    zoom() {
      debounceRealtimeCropper()
    },
    cropmove() {
      debounceRealtimeCropper()
    },
  })
}

const debounceRealtimeCropper = useDebounceFn(() => {
  realtimeCropper();
}, 80);

const realtimeCropper = () => {
  props.realTimePreview && croppered();
}

const croppered = () => {
  if (!cropper.value) {
    return;
  }
  let imgInfo = cropper.value.getData();
  const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas();
  canvas.toBlob((blob: Blob) => {
    if (!blob) {
      return;
    }
    let fileReader: FileReader = new FileReader();
    fileReader.readAsDataURL(blob);
    fileReader.onloadend = (e) => {
      emits('cropend', {imgBase64: e.target?.result ?? '', imgInfo,});
    };
    fileReader.onerror = () => {
      emits('error');
    };
  }, 'image/png');
}

const getRoundedCanvas = () => {
  const sourceCanvas = cropper.value!.getCroppedCanvas();
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d')!;
  const width = sourceCanvas.width;
  const height = sourceCanvas.height;
  canvas.width = width;
  canvas.height = height;
  context.imageSmoothingEnabled = true;
  context.drawImage(sourceCanvas, 0, 0, width, height);
  context.globalCompositeOperation = 'destination-in';
  context.beginPath();
  context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true);
  context.fill();
  return canvas;
}

const getWrapperStyle = computed((): CSSProperties => {
  return {height: `${props.height}`.replace(/px/, '') + 'px'};
});

const getImageStyle = computed((): CSSProperties => {
  return {
    height: props.height,
    maxWidth: '100%',
  };
});

onMounted(() => {
  initCropper();
});

onUnmounted(() => {
  cropper.value?.destroy();
});
</script>

<template>
  <div :style="getWrapperStyle">
    <img v-show="ready" ref="imgElRef" :src="src" :crossorigin="crossorigin" :style="getImageStyle" alt="avatar"/>
  </div>
</template>

<style scoped lang="scss">
:deep(.cropper-view-box) {
  border-radius: 50%;
}
</style>